<template>
  <div
    :class="computedClass" class="vdr"
    :style="{...positionStyle, ...sizeStyle, ...transitionStyle}"
    :data-droppable="droppable"
    @mousedown="(event) => bodyDown(event)"
    @touchstart="(event) => bodyDown(event)"
    @touchend.stop="(event) => up(event)"
    @dragenter.stop="dragEnter"
    @dragover.prevent=""
    @dragleave.stop="dragLeave"
    @drop.stop="drop">
    <slot></slot>
    <div
      v-for="(stick, idx) in sticks" :key="`stick${idx}`" :style="vdrStick(stick)"
      :class="['vdr-stick-' + stick, resizable ? '' : 'not-resizable']" class="vdr-stick"
      @mousedown.stop.prevent="(event) => stickDownFromEle(event, stick)"
      @touchstart.stop.prevent="(event) => stickDownFromEle(event, stick)">
    </div>
  </div>
</template>

<script>
  import {mapState, mapActions} from 'vuex';
  import {WidgetFactory} from "@/modules/IDE/widgetFactory";
  import {WidgetClass} from "@/modules/IDE/widget/atom/widget";
  import {deepCopy} from "@/utils";

  const styleMapping = {
    y: {
      t: 'top',
      m: 'marginTop',
      b: 'bottom'
    },
    x: {
      l: 'left',
      m: 'marginLeft',
      r: 'right'
    }
  };

  function addEvents(events) {
    // document.documentElement 页面根元素
    events.forEach((cb, eventName) => {
      document.documentElement.addEventListener(eventName, cb);
    });
  }

  function removeEvents(events) {
    events.forEach((cb, eventName) => {
      document.documentElement.removeEventListener(eventName, cb);
    });
  }

  export default {
    name: 'DragResize',
    emits: ['clicked', 'dragging', 'dragstop', 'resizing', 'resizestop', 'activated', 'deactivated'],
    props: {
      widget: {
        type: WidgetClass,
        default: () => {
          return {};
        }
      },
      stickSize: {
        type: Number,
	      default: 8
      },
      parentScaleX: {
        type: Number,
	      default: 1
      },
      parentScaleY: {
        type: Number,
	      default: 1
      },
      isActive: {
        type: Boolean,
	      default: false
      },
      preventActiveBehavior: {
        type: Boolean,
	      default: false
      },
      draggable: {
        type: Boolean,
	      default: true
      },
      droppable: {
        type: Boolean,
	      default: false
      },
      resizable: {
        type: Boolean,
	      default: true
      },
	    selectable: {
        type: Boolean,
		    default: true
      },
	    isLocked: {
        type: Boolean,
		    default: false
      },
	    isFreeze: {
        type: Boolean,
		    default: false
      },
      aspectRatio: {
        type: Boolean,
	      default: false
      },
      parentLimitation: {
        type: Boolean,
	      default: false
      },
      snapToGrid: {
        type: Boolean,
	      default: false
      },
      gridX: {
        type: Number,
        default: 50,
        validator(val) {
          return val >= 0;
        }
      },
      gridY: {
        type: Number,
        default: 50,
        validator(val) {
          return val >= 0;
        }
      },
      parentW: {
        type: Number,
        default: 0,
        validator(val) {
          return val >= 0;
        }
      },
      parentH: {
        type: Number,
        default: 0,
        validator(val) {
          return val >= 0;
        }
      },
	    initRect: {
		    type: Object,
		    default: () => {
			    return {
				    left: 0,
				    top: 0,
				    width: 200,
				    height: 200,
				    zIndex: 'auto'
			    };
		    }
	    },
      minw: {
        type: Number,
        default: 50,
        validator(val) {
          return val >= 0;
        }
      },
      minh: {
        type: Number,
        default: 50,
        validator(val) {
          return val >= 0;
        }
      },
      dragHandle: {
        type: String,
        default: null
      },
      dragCancel: {
        type: String,
        default: null
      },
      sticks: {
        type: Array,
        default() {
          return ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'];
        }
      },
      axis: {
        type: String,
        default: 'both',
        validator(val) {
          return ['x', 'y', 'both', 'none'].indexOf(val) !== -1;
        }
      },
      contentClass: {
        type: String,
        required: false,
        default: ''
      },
	    throttle: {
		    type: Boolean,
		    default: true
	    }
    },
    data() {
      return {
	      throttling: false,
	      throttlingTimer: null,
	      rect: {x: 0, y: 0, w: 200, h: 200, z: 'auto'},
        fixAspectRatio: null,
        zIndex: null,
        parentWidth: null,
        parentHeight: null,
        left: null,
        top: null,
        right: null,
        bottom: null,
        minHeight: null,
      };
    },
    computed: {
      ...mapState({
        draggingWidget: state => state.ide.draggingWidget,
        widgetAxis: state => state.ide.widgetAxis,
      }),
	    destRect() {
		    return {
			    left: Math.round(this.left),
			    top: Math.round(this.top),
			    width: Math.round(this.width),
			    height: Math.round(this.height),
			    zIndex: this.zIndex
		    };
	    },
      parentCategory() {
        const parentWidget= WidgetFactory.getWidgetById(this.widget.parentId) || {};
        return {
					'category': parentWidget.category,
	        'scrollDirection': parentWidget.category==='scroll' ? parentWidget.UI.data.scrollDirection : ''
				}
      },
      computedClass() {
        return [
          this.isActive ? 'active' : 'inactive',
          this.contentClass ? this.contentClass : '',
          this.widget.isHover && this.isActive ? 'vdr-hover' : ''
        ];
      },
      positionStyle() {
        return {
          top: this.top + 'px',
          left: this.left + 'px',
          zIndex: this.zIndex
        };
      },
      sizeStyle() {
        const {w, h} = this.rect;
        return {
          width: w === 'auto' ? 'auto' : this.width + 'px',
          height: h === 'auto' ? 'auto' : this.height + 'px'
        };
      },
	    transitionStyle() {
				if (!this.throttle) return {};
		    return this.throttling ? {
			    transition: 'left 150ms ease, top 150ms ease, width 150ms ease, height 150ms ease;'
		    } : {};
	    },
      vdrStick() {
        return (stick) => {
          const stickStyle = {
            width: `${this.stickSize / this.parentScaleX}px`,
            height: `${this.stickSize / this.parentScaleY}px`
          };
          stickStyle[styleMapping.y[stick[0]]] = `${this.stickSize / this.parentScaleX / -2}px`;
          stickStyle[styleMapping.x[stick[1]]] = `${this.stickSize / this.parentScaleX / -2}px`;
          stickStyle['display'] = !this.widget.hideStick && this.isActive ? 'block' : 'none';
          return stickStyle;
        };
      },
      width() {
        return this.parentWidth - this.left - this.right;
      },
      height() {
        return this.parentHeight - this.top - this.bottom;
      },
    },
    watch: {
			initRect: {
				handler(initRect) {
					// 初始化组件相对父组件的上、下、左、右位置
					const {left = 0, top = 0, width = 200, height = 200, zIndex = 'auto'} = deepCopy(initRect);
					this.rect = {x: left, y: top, w: width, h: height, z: zIndex};
					this.left = left;
					this.top = top;
					this.parentWidth = this.parentW;
					this.parentHeight = this.parentH;
					this.right = this.parentWidth - width - this.left;
					this.bottom = this.parentHeight - height - this.top;
				},
				immediate: true,
				deep: true
			},
      'rect.z': {
        immediate: true,
        handler(val) {
          if (val >= 0 || val === 'auto') {
            this.zIndex = val;
          }
        }
      },
      parentW(val) {
        this.right = val - this.width - this.left;
        this.parentWidth = val;
      },
      parentH(val) {
        this.bottom = val - this.height - this.top;
        this.parentHeight = val;
      }
    },
    beforeCreate() {
      this.stickDrag = false;
      this.bodyDrag = false;
      this.dimensionsBeforeMove = {pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0};
      this.limits = {
        left: {min: null, max: null},
        right: {min: null, max: null},
        top: {min: null, max: null},
        bottom: {min: null, max: null}
      };
      this.currentStick = null;
    },
    mounted() {
      this.domEvents = new Map([
        ['mousemove', this.move],
        ['mouseup', this.up],
        ['mouseleave', this.up],
        ['mousedown', this.deselect],
        ['touchmove', this.move],
        ['touchend', this.up],
        ['touchcancel', this.up],
        ['touchstart', this.up]
      ]);
      addEvents(this.domEvents);

      if (this.dragHandle) {
        [...this.$el.querySelectorAll(this.dragHandle)].forEach((dragHandle) => {
          dragHandle.setAttribute('data-drag-handle', this._uid);
        });
      }
      if (this.dragCancel) {
        [...this.$el.querySelectorAll(this.dragCancel)].forEach((cancelHandle) => {
          cancelHandle.setAttribute('data-drag-cancel', this._uid);
        });
      }
    },
    beforeDestroy() {
      removeEvents(this.domEvents);
    },
    methods: {
      ...mapActions(['updatePointerPosition']),
      // 点击到非组件以外的元素，则取消选择
      deselect() {
        // if (this.preventActiveBehavior) {
        //   return;
        // }
      },
      move(ev) {
				if (this.throttle && this.throttling) return;
        if (!this.stickDrag && !this.bodyDrag) {
          return;
        }
        ev.stopPropagation();

        const pageX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX;
        const pageY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY;

        const {dimensionsBeforeMove} = this;
        // 计算鼠标移动的 x、y 距离
        const delta = {
          x: (dimensionsBeforeMove.pointerX - pageX) / this.parentScaleX,
          y: (dimensionsBeforeMove.pointerY - pageY) / this.parentScaleY
        };

        if (this.stickDrag) {
          this.stickMove(delta);
        }
        if (this.bodyDrag) {
          // 判断是否设置只能单轴拖动，还是不可移动
          if (this.widgetAxis === 'horizontal') {
            delta.y = 0;
          } else if (this.widgetAxis === 'vertical') {
            delta.x = 0;
          } else if (this.widgetAxis === 'none') {
            return;
          }
          this.bodyMove(delta);
        }
      },
      up(ev) {
        if (this.stickDrag) {
          this.stickUp(ev);
        } else if (this.bodyDrag) {
          this.bodyUp(ev);
        }
      },
      // 选中一个组件时，进行初始化判断：是否能拖动等
      bodyDown(ev) {
	      if (this.throttle) {
		      // 在 150ms 后结束 throttle 行为
		      this.throttling = true;
		      this.throttlingTimer = setTimeout(() => {
			      this.throttling = false;
			      clearTimeout(this.throttlingTimer);
		      }, 150);
	      }
        const {target, button} = ev;
        if (button && button !== 0) {
          return;
        }
        if (this.dragHandle && target.getAttribute('data-drag-handle') !== this._uid.toString()) {
          return;
        }
        if (this.dragCancel && target.getAttribute('data-drag-cancel') === this._uid.toString()) {
          return;
        }

        // 对于可选中的 widget 阻止冒泡，不可选中的 widget 允许冒泡
        if (typeof ev.stopPropagation !== 'undefined') {
          if (this.selectable && !this.isFreeze) {
            ev.stopPropagation();
          }
        }
        if (typeof ev.preventDefault !== 'undefined') {
          ev.preventDefault();
        }
        // 这里判断是否选中框框的出现
        this.bodyDrag = true;
        const pointerX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX;
        const pointerY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY;
        this.saveDimensionsBeforeMove({pointerX, pointerY});
        if (['scroll'].includes(this.parentCategory.category)) {
          this.limits = this.calcDragLimits();
        }
        if (this.parentLimitation) {
          this.limits = this.calcDragLimits();
        }
      },
      bodyMove(delta) {
        // 不可拖拽或被锁定则直接返回
        if (!this.draggable || this.isLocked || this.isFreeze) return;
        const {dimensionsBeforeMove, parentWidth, parentHeight, gridX, gridY, width, height} = this;

        // 根据首次点击组件是，记录的 rect 和拖动时计算的鼠标移动距离，得出组件新的 rect
        let newTop = dimensionsBeforeMove.top - delta.y;
        let newBottom = dimensionsBeforeMove.bottom + delta.y;
        let newLeft = dimensionsBeforeMove.left - delta.x;
        let newRight = dimensionsBeforeMove.right + delta.x;

        // 判断是否只能根据步进值来拖拽组件
        if (this.snapToGrid) {
          let alignTop = true;
          let alignLeft = true;

          let diffT = newTop - Math.floor(newTop / gridY) * gridY;
          let diffB = (parentHeight - newBottom) - Math.floor((parentHeight - newBottom) / gridY) * gridY;
          let diffL = newLeft - Math.floor(newLeft / gridX) * gridX;
          let diffR = (parentWidth - newRight) - Math.floor((parentWidth - newRight) / gridX) * gridX;

          if (diffT > (gridY / 2)) {
            diffT -= gridY;
          }
          if (diffB > (gridY / 2)) {
            diffB -= gridY;
          }
          if (diffL > (gridX / 2)) {
            diffL -= gridX;
          }
          if (diffR > (gridX / 2)) {
            diffR -= gridX;
          }

          if (Math.abs(diffB) < Math.abs(diffT)) {
            alignTop = false;
          }
          if (Math.abs(diffR) < Math.abs(diffL)) {
            alignLeft = false;
          }

          newTop -= (alignTop ? diffT : diffB);
          newBottom = parentHeight - height - newTop;
          newLeft -= (alignLeft ? diffL : diffR);
          newRight = parentWidth - width - newLeft;
        }

        ({
          newLeft: this.left,
          newRight: this.right,
          newTop: this.top,
          newBottom: this.bottom
        } = this.rectCorrectionByLimit({newLeft, newRight, newTop, newBottom}));
        this.$emit('dragging', this.destRect);
      },
      bodyUp(ev) {
        this.bodyDrag = false;
        this.dimensionsBeforeMove = {pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0};
        this.limits = {
          left: {min: null, max: null},
          right: {min: null, max: null},
          top: {min: null, max: null},
          bottom: {min: null, max: null}
        };
        if (ev) {
          //保存当前鼠标拖拽的位置
          this.updatePointerPosition({
            offsetX: ev.offsetX,
            offsetY: ev.offsetY,
          });
          if (!this.selectable || this.isFreeze) return;
          this.$emit('clicked');
        }
        if (!this.selectable || this.isFreeze) return;
        this.$emit('dragstop', this.destRect);
      },
      stickDownFromEle(ev, stick) {
        // 不可拖拽或不可选中或被锁定则直接返回
        if (!this.resizable || this.isLocked || this.isFreeze) return;
        this.stickDown(ev, stick);
      },
      stickDown(ev, stick) {
        this.stickDrag = true;

        const pointerX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX;
        const pointerY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY;

        this.saveDimensionsBeforeMove({pointerX, pointerY});

        this.currentStick = stick;

        this.limits = this.calcResizeLimits();
      },
      stickMove(delta) {
        const {
          currentStick,
          dimensionsBeforeMove,
          gridY,
          gridX,
          snapToGrid,
          parentHeight,
          parentWidth
        } = this;

        let newTop = dimensionsBeforeMove.top;
        let newBottom = dimensionsBeforeMove.bottom;
        let newLeft = dimensionsBeforeMove.left;
        let newRight = dimensionsBeforeMove.right;
        if (!currentStick) return;
        switch (currentStick[0]) {
          case 'b':
            newBottom = dimensionsBeforeMove.bottom + delta.y;
            if (snapToGrid) {
              newBottom = parentHeight - Math.round((parentHeight - newBottom) / gridY) * gridY;
            }
            break;
          case 't':
            newTop = dimensionsBeforeMove.top - delta.y;
            if (snapToGrid) {
              newTop = Math.round(newTop / gridY) * gridY;
            }
            break;
          default:
            break;
        }
        switch (currentStick[1]) {
          case 'r':
            newRight = dimensionsBeforeMove.right + delta.x;
            if (snapToGrid) {
              newRight = parentWidth - Math.round((parentWidth - newRight) / gridX) * gridX;
            }
            break;
          case 'l':
            newLeft = dimensionsBeforeMove.left - delta.x;
            if (snapToGrid) {
              newLeft = Math.round(newLeft / gridX) * gridX;
            }
            break;
          default:
            break;
        }
        ({
          newLeft,
          newRight,
          newTop,
          newBottom
        } = this.rectCorrectionByLimit({newLeft, newRight, newTop, newBottom}));

        if (this.aspectRatio) {
          ({
            newLeft,
            newRight,
            newTop,
            newBottom
          } = this.rectCorrectionByAspectRatio({newLeft, newRight, newTop, newBottom}));
        }

        this.left = newLeft;
        this.right = newRight;
        this.top = newTop;
        this.bottom = newBottom;
        this.$emit('resizing', this.destRect, currentStick);
      },
      stickUp() {
        this.stickDrag = false;
        this.dimensionsBeforeMove = {pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0};
        this.limits = {
          left: {min: null, max: null},
          right: {min: null, max: null},
          top: {min: null, max: null},
          bottom: {min: null, max: null}
        };
        this.$emit('resizestop', this.destRect);
      },
      // 组件拖拽、移动前，把组件原来的 rect 以及鼠标点击的坐标记录下来
      saveDimensionsBeforeMove({pointerX, pointerY}) {
        this.dimensionsBeforeMove.pointerX = pointerX;
        this.dimensionsBeforeMove.pointerY = pointerY;

        this.dimensionsBeforeMove.left = this.left;
        this.dimensionsBeforeMove.right = this.right;
        this.dimensionsBeforeMove.top = this.top;
        this.dimensionsBeforeMove.bottom = this.bottom;

        this.dimensionsBeforeMove.width = this.width;
        this.dimensionsBeforeMove.height = this.height;

        this.aspectFactor = this.width / this.height;
      },
      // 向上、下、左、右，四个方向上拖动距离的限制，只用考虑是否超出父组件
      calcDragLimits() {
        const {parentWidth, parentHeight} = this;
        //判断是不是滚动容器 限制左右或上下
        if (['scroll'].includes(this.parentCategory.category)) {
          if (this.parentCategory.scrollDirection === 'horizontal') {
            return {
              left: {min: null, max: null},
              right: {min: null, max: null},
              top: {min: 0, max: parentHeight - this.height},
              bottom: {min: 0, max: parentHeight - this.height}
            };
          } else {
            return {
              left: {min: 0, max: parentWidth - this.width},
              right: {min: 0, max: parentWidth - this.width},
              top: {min: null, max: null},
              bottom: {min: null, max: null}
            };
          }
        } else {
          return {
            left: {min: 0, max: parentWidth - this.width},
            right: {min: 0, max: parentWidth - this.width},
            top: {min: 0, max: parentHeight - this.height},
            bottom: {min: 0, max: parentHeight - this.height}
          };
        }
      },
      // resize 的距离限制需要考虑外部设置的最小宽高
      calcResizeLimits() {
        const {aspectFactor, width, height, bottom, top, left, right} = this;
        let {minh: minHeight, minw: minWidth} = this;

        const parentLim = this.parentLimitation ? 0 : null;
        if (this.aspectRatio) {
          if (minWidth / minHeight > aspectFactor) {
            minHeight = minWidth / aspectFactor;
          } else {
            minWidth = aspectFactor * minHeight;
          }
        }

        const limits = {
          left: {min: parentLim, max: left + (width - minWidth)},
          right: {min: parentLim, max: right + (width - minWidth)},
          top: {min: parentLim, max: top + (height - minHeight)},
          bottom: {min: parentLim, max: bottom + (height - minHeight)}
        };

        if (this.aspectRatio) {
          const aspectLimits = {
            left: {
              min: left - (Math.min(top, bottom) * aspectFactor) * 2,
              max: left + ((((height - minHeight) / 2) * aspectFactor) * 2)
            },
            right: {
              min: right - (Math.min(top, bottom) * aspectFactor) * 2,
              max: right + ((((height - minHeight) / 2) * aspectFactor) * 2)
            },
            top: {
              min: top - (Math.min(left, right) / aspectFactor) * 2,
              max: top + ((((width - minWidth) / 2) / aspectFactor) * 2)
            },
            bottom: {
              min: bottom - (Math.min(left, right) / aspectFactor) * 2,
              max: bottom + ((((width - minWidth) / 2) / aspectFactor) * 2)
            }
          };

          if (this.currentStick[0] === 'm') {
            limits.left = {
              min: Math.max(limits.left.min, aspectLimits.left.min),
              max: Math.min(limits.left.max, aspectLimits.left.max)
            };
            limits.right = {
              min: Math.max(limits.right.min, aspectLimits.right.min),
              max: Math.min(limits.right.max, aspectLimits.right.max)
            };
          } else if (this.currentStick[1] === 'm') {
            limits.top = {
              min: Math.max(limits.top.min, aspectLimits.top.min),
              max: Math.min(limits.top.max, aspectLimits.top.max)
            };
            limits.bottom = {
              min: Math.max(limits.bottom.min, aspectLimits.bottom.min),
              max: Math.min(limits.bottom.max, aspectLimits.bottom.max)
            };
          }
        }

        return limits;
      },
      sideCorrectionByLimit(limit, current) {
        let value = current;
        if (limit.min !== null && current < limit.min) {
          value = limit.min;
        } else if (limit.max !== null && limit.max < current) {
          value = limit.max;
        }
        return value;
      },
      // 根据传入的 rect 以及 limits 计算校对过的新 rect
      rectCorrectionByLimit(rect) {
        const {limits} = this;
        let {newRight, newLeft, newBottom, newTop} = rect;
        newLeft = this.sideCorrectionByLimit(limits.left, newLeft);
        newRight = this.sideCorrectionByLimit(limits.right, newRight);
        newTop = this.sideCorrectionByLimit(limits.top, newTop);
        newBottom = this.sideCorrectionByLimit(limits.bottom, newBottom);
        return {
          newLeft,
          newRight,
          newTop,
          newBottom
        };
      },
      rectCorrectionByAspectRatio(rect) {
        let {newLeft, newRight, newTop, newBottom} = rect;
        const {parentWidth, parentHeight, currentStick, aspectFactor, dimensionsBeforeMove} = this;

        let newWidth = parentWidth - newLeft - newRight;
        let newHeight = parentHeight - newTop - newBottom;

        if (currentStick[1] === 'm') {
          const deltaHeight = newHeight - dimensionsBeforeMove.height;

          newLeft -= (deltaHeight * aspectFactor) / 2;
          newRight -= (deltaHeight * aspectFactor) / 2;
        } else if (currentStick[0] === 'm') {
          const deltaWidth = newWidth - dimensionsBeforeMove.width;

          newTop -= (deltaWidth / aspectFactor) / 2;
          newBottom -= (deltaWidth / aspectFactor) / 2;
        } else if (newWidth / newHeight > aspectFactor) {
          newWidth = aspectFactor * newHeight;

          if (currentStick[1] === 'l') {
            newLeft = parentWidth - newRight - newWidth;
          } else {
            newRight = parentWidth - newLeft - newWidth;
          }
        } else {
          newHeight = newWidth / aspectFactor;

          if (currentStick[0] === 't') {
            newTop = parentHeight - newBottom - newHeight;
          } else {
            newBottom = parentHeight - newTop - newHeight;
          }
        }
        return {newLeft, newRight, newTop, newBottom};
      },
      // 拖拽创建子组件相关处理
      dragEnter(event) {
        event.preventDefault();
				if (!this.draggingWidget) return;
	      // if (['dragLayout', 'deviceWidget'].includes(this.widget.category)) return;
        const droppableWidget = this.getDroppableParent(this.widget);
        droppableWidget.isHover = true;
        this.$EventBus.$emit('selectWidget', droppableWidget);
      },
	    dragLeave(event) {
		    event.preventDefault();
		    if (!this.draggingWidget) return;
		    const droppableWidget = this.getDroppableParent(this.widget);
		    droppableWidget.isHover = false;
	    },
      drop(event) {
	      if (!this.draggingWidget) return;
	      // if (['dragLayout', 'deviceWidget'].includes(this.widget.category)) return;
        const droppableWidget = this.getDroppableParent(this.widget);
        if (!droppableWidget) return;
        const eventOffset = {
          offsetX: event.offsetX,
          offsetY: event.offsetY
        };
        this.getDropWidgetOffset(eventOffset, event.target);
        this.updatePointerPosition(eventOffset);
        this.$EventBus.$emit('widgetDrop', droppableWidget);
      },
      // 递归找到首个 droppable 的父组件
      getDroppableParent(widget) {
        if (widget.droppable) return widget;
        const parent = WidgetFactory.getWidgetById(widget.parentId);
        if (!parent) return false;
        return this.getDroppableParent(parent);
      },
      // 递归计算当前拖拽组件相对于目标组件的坐标
      getDropWidgetOffset(eventOffset, target) {
        const {dataset, parentElement} = target;
        if (dataset.droppable === 'true') return;
        eventOffset.offsetX += target.offsetLeft;
        eventOffset.offsetY += target.offsetTop;
        parentElement && this.getDropWidgetOffset(eventOffset, parentElement);
      },
    }
  };
</script>

<style scoped lang="less">
  .vdr {
    position: absolute;
	  box-sizing: content-box !important;
  }

  .vdr-hover {
    animation: shadow 2s infinite;
  }

  @keyframes shadow {
    from {
      box-shadow: 0 0 0 @colorPrimary;
    }

    10% {
      box-shadow: 0 0 5px @colorPrimary;
    }

    20% {
      box-shadow: 0 0 10px @colorPrimary;
    }

    30% {
      box-shadow: 0 0 15px @colorPrimary;
    }

    40% {
      box-shadow: 0 0 20px @colorPrimary;
    }

    50% {
      box-shadow: 0 0 25px @colorPrimary;
    }

    60% {
      box-shadow: 0 0 20px @colorPrimary;
    }

    70% {
      box-shadow: 0 0 15px @colorPrimary;
    }

    80% {
      box-shadow: 0 0 10px @colorPrimary;
    }

    90% {
      box-shadow: 0 0 5px @colorPrimary;
    }

    to {
      box-shadow: 0 0 0px @colorPrimary;
    }
  }

  .vdr-stick {
    box-sizing: border-box;
    position: absolute;
    font-size: 1px;
    background: #ffffff;
    border: 1px solid @colorPrimary;
    box-shadow: 0 0 2px #bbb;
  }

  .inactive .vdr-stick {
    display: none;
  }

  .vdr-stick-tl,
  .vdr-stick-br {
    cursor: nwse-resize;
  }

  .vdr-stick-tm,
  .vdr-stick-bm {
    left: 50%;
    cursor: ns-resize;
  }

  .vdr-stick-tr,
  .vdr-stick-bl {
    cursor: nesw-resize;
  }

  .vdr-stick-ml,
  .vdr-stick-mr {
    top: 50%;
    cursor: ew-resize;
  }

  .vdr-stick.not-resizable {
    display: none;
  }

  .content-container {
    display: block;
    position: relative;
  }
</style>
